//prodcon.cc
//      Implement the producer/consumer problem using Condition variables.
#include "copyright.h"
#include "system.h"
#include "synch.h"

static Lock* monitor_lock = NULL;
static Condition* wait_space = NULL;
static Condition* wait_content = NULL;

static char* buf = NULL;
static int buf_storage = 0;
static int buf_size = 0;
static int buf_start = 0;

//Use a monitor to put a character in a buffer.
static void
monitor_put(char c) {
    ASSERT(NULL != monitor_lock);

    monitor_lock->Acquire();

    while(buf_storage == buf_size) {
        wait_space->Wait(monitor_lock);
    }
    wait_content->Signal(monitor_lock);

    buf[(buf_start + buf_size) % buf_storage] = c;
    buf_size++;

    printf("put - '%c'\n", c);

    monitor_lock->Release();
}

//Use a monitor to get a character from a buffer.
static char
monitor_get() {
    char rtn = '\0';

    ASSERT(NULL != monitor_lock);
    monitor_lock->Acquire();

    while(buf_size == 0) {
        wait_content->Wait(monitor_lock);
    }
    wait_space->Signal(monitor_lock);

    rtn = buf[buf_start];
    buf_start = (buf_start + 1) % buf_storage;
    buf_size--;

    printf(" get - '%c'\n", rtn);

    monitor_lock->Release();

    return rtn;
}

static void
producer(int which) {
    static const char hello[] = "Hello world";

    for(int i = 0; hello[i] != '\0'; i++) {
        monitor_put(hello[i]);
    }
}

static void
consumer(int which) {
    char consumed = '\0';

    while(true) {
        consumed = monitor_get();
    }
}

//----------------------------------------------------------------------
// ThreadTest
//----------------------------------------------------------------------

void
ThreadTest() {
    int num_prod = 3;
    int num_con = 3;
    Thread* threads[num_prod + num_con];

    DEBUG('t', "Entering Producer/Consumer test.");

    buf_storage = 50;
    buf = new char[buf_storage];
    monitor_lock = new Lock("monitor_lock");
    wait_space = new Condition("wait_space");
    wait_content = new Condition("wait_content");

    for(int i = 0; i < num_prod; i++) {
        threads[i] = new Thread("Procucer");
        threads[i]->Fork(producer, i);
    }
    for(int i = 0; i < num_con; i++) {
        threads[num_prod + i] = new Thread("Consumer", 10);
        threads[num_prod + i]->Fork(consumer, i);
    }
}

